const fs = require('fs');
const path = require('path');

// 激活函数
function sigmoid(x) {
  return 1 / (1 + Math.exp(-x));
}

// 激活函数的导数
function sigmoidDerivative(x) {
  return x * (1 - x);
}

// 简单的神经网络类
class NeuralNetwork {
  constructor(inputSize, hiddenSize, outputSize) {
    this.inputSize = inputSize;
    this.hiddenSize = hiddenSize;
    this.outputSize = outputSize;

    // 初始化权重
    this.weightsInputHidden = [];
    for (let i = 0; i < this.inputSize; i++) {
      this.weightsInputHidden[i] = [];
      for (let j = 0; j < this.hiddenSize; j++) {
        this.weightsInputHidden[i][j] = Math.random() * 2 - 1;
      }
    }

    this.weightsHiddenOutput = [];
    for (let i = 0; i < this.hiddenSize; i++) {
      this.weightsHiddenOutput[i] = [];
      for (let j = 0; j < this.outputSize; j++) {
        this.weightsHiddenOutput[i][j] = Math.random() * 2 - 1;
      }
    }
  }

  // 前向传播
  feedForward(input) {
    let hidden = [];
    for (let j = 0; j < this.hiddenSize; j++) {
      let sum = 0;
      for (let i = 0; i < this.inputSize; i++) {
        sum += input[i] * this.weightsInputHidden[i][j];
      }
      hidden[j] = sigmoid(sum);
    }

    let output = [];
    for (let k = 0; k < this.outputSize; k++) {
      let sum = 0;
      for (let j = 0; j < this.hiddenSize; j++) {
        sum += hidden[j] * this.weightsHiddenOutput[j][k];
      }
      output[k] = sigmoid(sum);
    }

    return output;
  }

  // 反向传播
  train(input, target, learningRate) {
    // 前向传播
    let hidden = [];
    for (let j = 0; j < this.hiddenSize; j++) {
      let sum = 0;
      for (let i = 0; i < this.inputSize; i++) {
        sum += input[i] * this.weightsInputHidden[i][j];
      }
      hidden[j] = sigmoid(sum);
    }

    let output = [];
    for (let k = 0; k < this.outputSize; k++) {
      let sum = 0;
      for (let j = 0; j < this.hiddenSize; j++) {
        sum += hidden[j] * this.weightsHiddenOutput[j][k];
      }
      output[k] = sigmoid(sum);
    }

    // 计算输出层误差
    let outputError = [];
    for (let k = 0; k < this.outputSize; k++) {
      outputError[k] = target[k] - output[k];
    }

    // 计算输出层梯度
    let outputGradient = [];
    for (let k = 0; k < this.outputSize; k++) {
      outputGradient[k] = outputError[k] * sigmoidDerivative(output[k]);
    }

    // 计算隐藏层误差
    let hiddenError = [];
    for (let j = 0; j < this.hiddenSize; j++) {
      let sum = 0;
      for (let k = 0; k < this.outputSize; k++) {
        sum += outputGradient[k] * this.weightsHiddenOutput[j][k];
      }
      hiddenError[j] = sum;
    }

    // 计算隐藏层梯度
    let hiddenGradient = [];
    for (let j = 0; j < this.hiddenSize; j++) {
      hiddenGradient[j] = hiddenError[j] * sigmoidDerivative(hidden[j]);
    }

    // 更新权重
    for (let j = 0; j < this.hiddenSize; j++) {
      for (let k = 0; k < this.outputSize; k++) {
        this.weightsHiddenOutput[j][k] += learningRate * outputGradient[k] * hidden[j];
      }
    }

    for (let i = 0; i < this.inputSize; i++) {
      for (let j = 0; j < this.hiddenSize; j++) {
        this.weightsInputHidden[i][j] += learningRate * hiddenGradient[j] * input[i];
      }
    }
  }

  // 保存权重到文件
  saveWeights(filePath) {
    const weights = {
      weightsInputHidden: this.weightsInputHidden,
      weightsHiddenOutput: this.weightsHiddenOutput
    };
    const jsonWeights = JSON.stringify(weights);
    fs.writeFileSync(filePath, jsonWeights);
  }

  // 从文件加载权重
  loadWeights(filePath) {
    if (fs.existsSync(filePath)) {
      const jsonWeights = fs.readFileSync(filePath, 'utf8');
      const weights = JSON.parse(jsonWeights);
      this.weightsInputHidden = weights.weightsInputHidden;
      this.weightsHiddenOutput = weights.weightsHiddenOutput;
    }
  }
}

// 井字棋类
class TicTacToe {
  constructor(firstPlayer) {
    this.board = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
    this.currentPlayer = firstPlayer;
    this.moves = [];
  }

  // 打印棋盘，为未落子位置标上数字
  printBoard() {
    let boardToPrint = [];
    for (let i = 0; i < this.board.length; i++) {
      if (this.board[i] === ' ') {
        boardToPrint[i] = i.toString();
      } else {
        boardToPrint[i] = this.board[i];
      }
    }
    console.log(`${boardToPrint[0]} | ${boardToPrint[1]} | ${boardToPrint[2]}`);
    console.log('---------');
    console.log(`${boardToPrint[3]} | ${boardToPrint[4]} | ${boardToPrint[5]}`);
    console.log('---------');
    console.log(`${boardToPrint[6]} | ${boardToPrint[7]} | ${boardToPrint[8]}`);
  }

  // 检查是否有玩家获胜
  checkWin(player) {
    const winPatterns = [
      [0, 1, 2], [3, 4, 5], [6, 7, 8], // 行
      [0, 3, 6], [1, 4, 7], [2, 5, 8], // 列
      [0, 4, 8], [2, 4, 6] // 对角线
    ];

    for (let pattern of winPatterns) {
      if (
        this.board[pattern[0]] === player &&
        this.board[pattern[1]] === player &&
        this.board[pattern[2]] === player
      ) {
        return true;
      }
    }

    return false;
  }

  // 检查是否平局
  checkDraw() {
    return this.board.every(cell => cell !== ' ');
  }

  // 落子
  makeMove(position) {
    if (this.board[position] === ' ') {
      this.board[position] = this.currentPlayer;
      this.moves.push({ state: this.getBoardState(), move: position });
      return true;
    }
    return false;
  }

  // 切换玩家
  switchPlayer() {
    this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X';
  }

  // 获取当前棋盘状态
  getBoardState() {
    return this.board.map(cell => cell === ' ' ? 0 : (cell === 'X' ? 1 : -1));
  }
}

// 生成棋谱
function generateGameRecords() {
  let records = [];
  for (let i = 0; i < 200000; i++) {
    let game = new TicTacToe(Math.random() > 0.5 ? 'X' : 'O');
    let moves = [];
    while (true) {
      let availableMoves = [];
      for (let j = 0; j < 9; j++) {
        if (game.board[j] === ' ') {
          availableMoves.push(j);
        }
      }
      let randomMove = availableMoves[Math.floor(Math.random() * availableMoves.length)];
      moves.push({ state: game.getBoardState(), move: randomMove });
      game.makeMove(randomMove);
      if (game.checkWin(game.currentPlayer)) {
        let winner = game.currentPlayer;
        for (let move of moves) {
          let target = new Array(9).fill(0);
          target[move.move] = winner === 'X' ? 1 : -1;
          records.push({ input: move.state, target: target });
        }
        break;
      } else if (game.checkDraw()) {
        for (let move of moves) {
          let target = new Array(9).fill(0);
          records.push({ input: move.state, target: target });
        }
        break;
      }
      game.switchPlayer();
    }
  }
  return records;
}

// 训练神经网络
function trainNeuralNetwork(network, records, learningRate, epochs) {
  for (let epoch = 0; epoch < epochs; epoch++) {
    for (let record of records) {
      network.train(record.input, record.target, learningRate);
    }
  }
}

// 把 readline.question 封装成 Promise
function askQuestion(readline, question) {
  return new Promise((resolve) => {
    readline.question(question, (answer) => {
      resolve(answer);
    });
  });
}

// 人机对战
async function playAgainstAI(network, weightsFilePath) {
  let firstPlayer = 'X'; // 先手为玩家
  let gameCount = 1;
  let playerScore = 0;
  let aiScore = 0;

  while (true) {
    console.log(`===========第${gameCount}局===========`);
    let game = new TicTacToe(firstPlayer);
    let readline = require('readline').createInterface({
      input: process.stdin,
      output: process.stdout
    });
    while (true) {
      game.printBoard();
      if (game.currentPlayer === 'X') {
        let position = await askQuestion(readline, '请输入落子位置 (0 - 8): ');
        position = parseInt(position);
        if (game.makeMove(position)) {
          if (game.checkWin(game.currentPlayer)) {
            game.printBoard();
            if (game.currentPlayer === 'X') {
              console.log('你赢了！');
              playerScore++;
            } else {
              console.log('AI 赢了！');
              aiScore++;
            }
            trainGameResult(network, game, 'X', 0.1);
            network.saveWeights(weightsFilePath);
            readline.close();
            console.log(`*******************第${gameCount}局比分：你(${playerScore}):AI(${aiScore})*******************\n\n`);
            break;
          } else if (game.checkDraw()) {
            game.printBoard();
            console.log('平局！');
            readline.close();
            console.log(`******************第${gameCount}局*比分：你(${playerScore}):AI(${aiScore})*******************\n\n`);
            break;
          }
          console.log(`------------------------- 你 落子在位置 ${position}`);
          game.switchPlayer();
        } else {
          console.log('该位置已被占用，请重新选择。');
        }
      } else {
        let output = network.feedForward(game.getBoardState());
        let availableMoves = [];
        for (let j = 0; j < 9; j++) {
          if (game.board[j] === ' ') {
            availableMoves.push(j);
          }
        }
        let bestMove = availableMoves[0];
        let bestScore = output[bestMove];
        for (let move of availableMoves) {
          if (output[move] > bestScore) {
            bestMove = move;
            bestScore = output[move];
          }
        }
        game.makeMove(bestMove);
        if (game.checkWin(game.currentPlayer)) {
          game.printBoard();
          if (game.currentPlayer === 'X') {
            console.log('AI 赢了！');
            aiScore++;
          } else {
            console.log('你赢了！');
            playerScore++;
          }
          trainGameResult(network, game, 'O', 0.1);
          network.saveWeights(weightsFilePath);
          readline.close();
          console.log(`*******************第${gameCount}局比分：你(${playerScore}):AI(${aiScore})*******************\n\n`);
          break;
        } else if (game.checkDraw()) {
          game.printBoard();
          console.log('平局！');
          readline.close();
          console.log(`*******************第${gameCount}局比分：你(${playerScore}):AI(${aiScore})*******************\n\n`);
          break;
        }
        console.log(`------------------------- AI 落子在位置 ${bestMove}`);
        game.switchPlayer();
      }
    }
    firstPlayer = firstPlayer === 'X' ? 'O' : 'X'; // 交换先手
    gameCount++;
  }
}

// 根据对战结果训练神经网络
function trainGameResult(network, game, winner, learningRate) {
  for (let move of game.moves) {
    let target = new Array(9).fill(0);
    target[move.move] = winner === 'X' ? 1 : -1;
    network.train(move.state, target, learningRate);
  }
}

// 主程序封装到 main 函数
async function main() {
  const weightsFilePath = path.join(__dirname, 'weights.json');
  let network = new NeuralNetwork(9, 10, 9);
  network.loadWeights(weightsFilePath);

  if (!fs.existsSync(weightsFilePath)) {
    let records = generateGameRecords();
    trainNeuralNetwork(network, records, 0.1, 10);
    network.saveWeights(weightsFilePath);
  }

  await playAgainstAI(network, weightsFilePath);
}

// 调用 main 函数启动程序
main().catch((error) => {
  console.error('程序出错:', error);
});

